Add new infrastructure for notifications of failed keyboard navigation and
authorMichael Natterer <mitch@imendio.com>
Thu, 16 Nov 2006 12:56:30 +0000 (12:56 +0000)
committerMichael Natterer <mitch@src.gnome.org>
Thu, 16 Nov 2006 12:56:30 +0000 (12:56 +0000)
2006-11-16  Michael Natterer  <mitch@imendio.com>

Add new infrastructure for notifications of failed keyboard
navigation and navigation with restricted set of keys.

The patch handles configurable beeping, navigating the GUI with
cursor keys only (as in phone environments), and configurable
wrap-around. Fixes bugs #322640, #70986, #318827, #334726, #334742
and #309291.

* gtk/gtksettings.c: added properties gtk-keynav-cursor-only,
gtk-keynav-wrap-around and gtk-error-bell.

* gtk/gtkwidget.[ch]: added new signal "keynav-failed" and public
API to emit it. Added New function gtk_widget_error_bell() which
looks at the gtk-error-bell setting and calls gdk_window_beep()
accordingly.

* gtk/gtk.symbols: add the new widget symbols.

* gtk/gtkcellrendereraccel.c
* gtk/gtkimcontextsimple.c
* gtk/gtkmenu.c
* gtk/gtknotebook.c: use gtk_widget_error_bell() or look at the
gtk-error-bell setting instead of calling gdk_display_beep()
unconditionally.

* gtk/gtkcombobox.c
* gtk/gtkentry.c
* gtk/gtkiconview.c
* gtk/gtklabel.c
* gtk/gtkmenushell.c
* gtk/gtkspinbutton.c
* gtk/gtktextview.c
* gtk/gtktreeview.c: call gtk_widget_error_bell() on failed keynav.

* gtk/gtkentry.c
* gtk/gtklabel.c
* gtk/gtkrange.c
* gtk/gtktextview.c: consult gtk_widget_keynav_failed() on failed
cursor navigation and leave the widget if it returns FALSE.

* gtk/gtkmenushell.c
* gtk/gtknotebook.c: only wrap around if gtk-keynav-wrap-around
is TRUE.

* gtk/gtkradiobutton.c: ask gtk_widget_keynav_failed() to decide
whether to to wrap-around, and don't select active items on cursor
navigation if gtk-keynav-cursor-only is TRUE. Should look at
gtk-keynav-wrap-around too, will look into that.

19 files changed:
ChangeLog
gtk/gtk.symbols
gtk/gtkcellrendereraccel.c
gtk/gtkcombobox.c
gtk/gtkentry.c
gtk/gtkiconview.c
gtk/gtkimcontextsimple.c
gtk/gtklabel.c
gtk/gtkmenu.c
gtk/gtkmenushell.c
gtk/gtknotebook.c
gtk/gtkradiobutton.c
gtk/gtkrange.c
gtk/gtksettings.c
gtk/gtkspinbutton.c
gtk/gtktextview.c
gtk/gtktreeview.c
gtk/gtkwidget.c
gtk/gtkwidget.h

index 0a95d7fdf7711e7e7d9cd2a5895dfa1d691e94b4..129b65320096eb6fbae89e53d0d7d6b41b31c492 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,54 @@
+2006-11-16  Michael Natterer  <mitch@imendio.com>
+
+       Add new infrastructure for notifications of failed keyboard
+       navigation and navigation with restricted set of keys.
+
+       The patch handles configurable beeping, navigating the GUI with
+       cursor keys only (as in phone environments), and configurable
+       wrap-around. Fixes bugs #322640, #70986, #318827, #334726, #334742
+       and #309291.
+
+       * gtk/gtksettings.c: added properties gtk-keynav-cursor-only,
+       gtk-keynav-wrap-around and gtk-error-bell.
+
+       * gtk/gtkwidget.[ch]: added new signal "keynav-failed" and public
+       API to emit it. Added New function gtk_widget_error_bell() which
+       looks at the gtk-error-bell setting and calls gdk_window_beep()
+       accordingly.
+
+       * gtk/gtk.symbols: add the new widget symbols.
+
+       * gtk/gtkcellrendereraccel.c
+       * gtk/gtkimcontextsimple.c
+       * gtk/gtkmenu.c
+       * gtk/gtknotebook.c: use gtk_widget_error_bell() or look at the
+       gtk-error-bell setting instead of calling gdk_display_beep()
+       unconditionally.
+
+       * gtk/gtkcombobox.c
+       * gtk/gtkentry.c
+       * gtk/gtkiconview.c
+       * gtk/gtklabel.c
+       * gtk/gtkmenushell.c
+       * gtk/gtkspinbutton.c
+       * gtk/gtktextview.c
+       * gtk/gtktreeview.c: call gtk_widget_error_bell() on failed keynav.
+
+       * gtk/gtkentry.c
+       * gtk/gtklabel.c
+       * gtk/gtkrange.c
+       * gtk/gtktextview.c: consult gtk_widget_keynav_failed() on failed
+       cursor navigation and leave the widget if it returns FALSE.
+
+       * gtk/gtkmenushell.c
+       * gtk/gtknotebook.c: only wrap around if gtk-keynav-wrap-around
+       is TRUE.
+
+       * gtk/gtkradiobutton.c: ask gtk_widget_keynav_failed() to decide
+       whether to to wrap-around, and don't select active items on cursor
+       navigation if gtk-keynav-cursor-only is TRUE. Should look at
+       gtk-keynav-wrap-around too, will look into that.
+
 2006-11-16  Emmanuele Bassi  <ebassi@gnome.org>
 
        * gtk/gtkrecentmanager.c:
index 61343e4a08d6935f9336aea51f96f78e8be06592..939370a65d919f4fd01621ebbb288c0e6b09b292 100644 (file)
@@ -4470,6 +4470,7 @@ gtk_widget_create_pango_layout
 gtk_widget_destroy
 gtk_widget_destroyed
 gtk_widget_ensure_style
+gtk_widget_error_bell
 gtk_widget_event
 gtk_widget_freeze_child_notify
 gtk_widget_get_accessible
@@ -4511,6 +4512,7 @@ gtk_widget_hide_on_delete
 gtk_widget_intersect
 gtk_widget_is_ancestor
 gtk_widget_is_focus
+gtk_widget_keynav_failed
 gtk_widget_list_accel_closures
 gtk_widget_list_mnemonic_labels
 gtk_widget_map
index 31b70c3e7e148538aeed563a81703cae91396555..9157fc2b60c1dac71aa41ba7d25a5da1c9095687 100644 (file)
@@ -456,7 +456,7 @@ grab_key_callback (GtkWidget    *widget,
     {
       if (!gtk_accelerator_valid (accel_key, accel_mods))
        {
-         gdk_display_beep (display);
+         gtk_widget_error_bell (widget);
 
          return TRUE;
        }
index 1cf287fe0f616241e8e404017b77503553a7003f..c1d2bdd946c5a2006870ffb17bdb39bda074bc72 100644 (file)
@@ -4978,7 +4978,10 @@ gtk_combo_box_real_move_active (GtkComboBox   *combo_box,
   gboolean    found;
 
   if (!combo_box->priv->model)
-    return;
+    {
+      gtk_widget_error_bell (GTK_WIDGET (combo_box));
+      return;
+    }
 
   active_iter = gtk_combo_box_get_active_iter (combo_box, &iter);
 
@@ -5024,28 +5027,28 @@ gtk_combo_box_real_move_active (GtkComboBox   *combo_box,
       return;
     }
 
-  if (found)
+  if (found && active_iter)
     {
-      if (active_iter)
-        {
-          GtkTreePath *old_path;
-          GtkTreePath *new_path;
+      GtkTreePath *old_path;
+      GtkTreePath *new_path;
 
-          old_path = gtk_tree_model_get_path (combo_box->priv->model, &iter);
-          new_path = gtk_tree_model_get_path (combo_box->priv->model, &new_iter);
+      old_path = gtk_tree_model_get_path (combo_box->priv->model, &iter);
+      new_path = gtk_tree_model_get_path (combo_box->priv->model, &new_iter);
 
-          if (gtk_tree_path_compare (old_path, new_path) == 0)
-            found = FALSE;
+      if (gtk_tree_path_compare (old_path, new_path) == 0)
+        found = FALSE;
 
-          gtk_tree_path_free (old_path);
-          gtk_tree_path_free (new_path);
-        }
+      gtk_tree_path_free (old_path);
+      gtk_tree_path_free (new_path);
+    }
 
-      if (found)
-        {
-          gtk_combo_box_set_active_iter (combo_box, &new_iter);
-          return;
-        }
+  if (found)
+    {
+      gtk_combo_box_set_active_iter (combo_box, &new_iter);
+    }
+  else
+    {
+      gtk_widget_error_bell (GTK_WIDGET (combo_box));
     }
 }
 
index 701c4347a470fc77697f6e8d4cd47590f8f79fbd..30f96f1072bd591968ced65139dc966153b6dde5 100644 (file)
@@ -1709,12 +1709,18 @@ gtk_entry_button_press (GtkWidget      *widget,
 
       return TRUE;
     }
-  else if (event->button == 2 && event->type == GDK_BUTTON_PRESS && entry->editable)
+  else if (event->button == 2 && event->type == GDK_BUTTON_PRESS)
     {
-      priv->insert_pos = tmp_pos;
-      gtk_entry_paste (entry, GDK_SELECTION_PRIMARY);
-
-      return TRUE;
+      if (entry->editable)
+        {
+          priv->insert_pos = tmp_pos;
+          gtk_entry_paste (entry, GDK_SELECTION_PRIMARY);
+          return TRUE;
+        }
+      else
+        {
+          gtk_widget_error_bell (widget);
+        }
     }
   else if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
     {
@@ -1976,6 +1982,9 @@ gtk_entry_key_press (GtkWidget   *widget,
      */
     return TRUE;
 
+  if (!entry->editable && event->length)
+    gtk_widget_error_bell (widget);
+
   return FALSE;
 }
 
@@ -2329,7 +2338,7 @@ gtk_entry_real_insert_text (GtkEditable *editable,
   n_chars = g_utf8_strlen (new_text, new_text_length);
   if (entry->text_max_length > 0 && n_chars + entry->text_length > entry->text_max_length)
     {
-      gdk_display_beep (gtk_widget_get_display (GTK_WIDGET (entry)));
+      gtk_widget_error_bell (GTK_WIDGET (entry));
       n_chars = entry->text_max_length - entry->text_length;
       new_text_length = g_utf8_offset_to_pointer (new_text, n_chars) - new_text;
     }
@@ -2535,7 +2544,6 @@ gtk_entry_move_cursor (GtkEntry       *entry,
              new_pos = current_x < bound_x ? entry->current_pos : entry->selection_bound;
            else 
              new_pos = current_x > bound_x ? entry->current_pos : entry->selection_bound;
-
            break;
          }
        case GTK_MOVEMENT_LOGICAL_POSITIONS:
@@ -2566,6 +2574,27 @@ gtk_entry_move_cursor (GtkEntry       *entry,
          break;
        case GTK_MOVEMENT_VISUAL_POSITIONS:
          new_pos = gtk_entry_move_visually (entry, new_pos, count);
+          if (entry->current_pos == new_pos)
+            {
+              if (!extend_selection)
+                {
+                  if (!gtk_widget_keynav_failed (GTK_WIDGET (entry),
+                                                 count > 0 ?
+                                                 GTK_DIR_RIGHT : GTK_DIR_LEFT))
+                    {
+                      GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (entry));
+
+                      if (toplevel)
+                        gtk_widget_child_focus (toplevel,
+                                                count > 0 ?
+                                                GTK_DIR_RIGHT : GTK_DIR_LEFT);
+                    }
+                }
+              else
+                {
+                  gtk_widget_error_bell (GTK_WIDGET (entry));
+                }
+            }
          break;
        case GTK_MOVEMENT_WORDS:
          while (count > 0)
@@ -2578,11 +2607,15 @@ gtk_entry_move_cursor (GtkEntry       *entry,
              new_pos = gtk_entry_move_backward_word (entry, new_pos, FALSE);
              count++;
            }
+          if (entry->current_pos == new_pos)
+            gtk_widget_error_bell (GTK_WIDGET (entry));
          break;
        case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
        case GTK_MOVEMENT_PARAGRAPH_ENDS:
        case GTK_MOVEMENT_BUFFER_ENDS:
          new_pos = count < 0 ? 0 : entry->text_length;
+          if (entry->current_pos == new_pos)
+            gtk_widget_error_bell (GTK_WIDGET (entry));
          break;
        case GTK_MOVEMENT_DISPLAY_LINES:
        case GTK_MOVEMENT_PARAGRAPHS:
@@ -2624,11 +2657,15 @@ gtk_entry_delete_from_cursor (GtkEntry       *entry,
   GtkEditable *editable = GTK_EDITABLE (entry);
   gint start_pos = entry->current_pos;
   gint end_pos = entry->current_pos;
+  gint old_n_bytes = entry->n_bytes;
   
   _gtk_entry_reset_im_context (entry);
 
   if (!entry->editable)
-    return;
+    {
+      gtk_widget_error_bell (GTK_WIDGET (entry));
+      return;
+    }
 
   if (entry->selection_bound != entry->current_pos)
     {
@@ -2685,7 +2722,10 @@ gtk_entry_delete_from_cursor (GtkEntry       *entry,
       gtk_entry_delete_whitespace (entry);
       break;
     }
-  
+
+  if (entry->n_bytes == old_n_bytes)
+    gtk_widget_error_bell (GTK_WIDGET (entry));
+
   gtk_entry_pend_cursor_blink (entry);
 }
 
@@ -2698,7 +2738,10 @@ gtk_entry_backspace (GtkEntry *entry)
   _gtk_entry_reset_im_context (entry);
 
   if (!entry->editable || !entry->text)
-    return;
+    {
+      gtk_widget_error_bell (GTK_WIDGET (entry));
+      return;
+    }
 
   if (entry->selection_bound != entry->current_pos)
     {
@@ -2751,6 +2794,10 @@ gtk_entry_backspace (GtkEntry *entry)
       
       g_free (log_attrs);
     }
+  else
+    {
+      gtk_widget_error_bell (GTK_WIDGET (entry));
+    }
 
   gtk_entry_pend_cursor_blink (entry);
 }
@@ -2784,6 +2831,10 @@ gtk_entry_cut_clipboard (GtkEntry *entry)
       if (gtk_editable_get_selection_bounds (editable, &start, &end))
        gtk_editable_delete_text (editable, start, end);
     }
+  else
+    {
+      gtk_widget_error_bell (GTK_WIDGET (entry));
+    }
 }
 
 static void
@@ -2791,6 +2842,8 @@ gtk_entry_paste_clipboard (GtkEntry *entry)
 {
   if (entry->editable)
     gtk_entry_paste (entry, GDK_NONE);
+  else
+    gtk_widget_error_bell (GTK_WIDGET (entry));
 }
 
 static void
index 9e066cf938504165ea88c150f8f408184e1734e7..1ffcddb23673e9ae6e50d940c8c8f220ed29f2f4 100644 (file)
@@ -3796,7 +3796,10 @@ gtk_icon_view_move_cursor_up_down (GtkIconView *icon_view,
     }
 
   if (!item)
-    return;
+    {
+      gtk_widget_error_bell (GTK_WIDGET (icon_view));
+      return;
+    }
 
   if (icon_view->priv->ctrl_pressed ||
       !icon_view->priv->shift_pressed ||
@@ -3847,6 +3850,9 @@ gtk_icon_view_move_cursor_page_up_down (GtkIconView *icon_view,
                                   icon_view->priv->cursor_item,
                                   count);
 
+  if (item == icon_view->priv->cursor_item)
+    gtk_widget_error_bell (GTK_WIDGET (icon_view));
+
   if (!item)
     return;
 
@@ -3915,7 +3921,10 @@ gtk_icon_view_move_cursor_left_right (GtkIconView *icon_view,
     }
 
   if (!item)
-    return;
+    {
+      gtk_widget_error_bell (GTK_WIDGET (icon_view));
+      return;
+    }
 
   if (icon_view->priv->ctrl_pressed ||
       !icon_view->priv->shift_pressed ||
@@ -3958,6 +3967,9 @@ gtk_icon_view_move_cursor_start_end (GtkIconView *icon_view,
   
   item = list ? list->data : NULL;
 
+  if (item == icon_view->priv->cursor_item)
+    gtk_widget_error_bell (GTK_WIDGET (icon_view));
+
   if (!item)
     return;
 
index e6267e514e0259f5bc4bdb7dec81f7472e463245..ea4182bd0aa36b0095da508de9d4a6a142916a6e 100644 (file)
@@ -23,6 +23,8 @@
 #include <gdk/gdkkeysyms.h>
 #include "gtkaccelgroup.h"
 #include "gtkimcontextsimple.h"
+#include "gtksettings.h"
+#include "gtkwidget.h"
 #include "gtkintl.h"
 #include "gtkalias.h"
 
@@ -1175,6 +1177,31 @@ check_hex (GtkIMContextSimple *context_simple,
   return TRUE;
 }
 
+static void
+beep_window (GdkWindow *window)
+{
+  GtkWidget *widget;
+
+  gdk_window_get_user_data (window, &widget);
+
+  if (GTK_IS_WIDGET (widget))
+    {
+      gtk_widget_error_bell (widget);
+    }
+  else
+    {
+      GdkScreen *screen = gdk_drawable_get_screen (GDK_DRAWABLE (window));
+      gboolean   beep;
+
+      g_object_get (gtk_settings_get_for_screen (screen),
+                    "gtk-error-bell", &beep,
+                    NULL);
+
+      if (beep)
+        gdk_window_beep (window);
+    }
+}
+
 static gboolean
 no_sequence_matches (GtkIMContextSimple *context_simple,
                      gint                n_compose,
@@ -1212,7 +1239,7 @@ no_sequence_matches (GtkIMContextSimple *context_simple,
       context_simple->compose_buffer[0] = 0;
       if (n_compose > 1)               /* Invalid sequence */
        {
-         gdk_display_beep (gdk_drawable_get_display (event->window));
+         beep_window (event->window);
          return TRUE;
        }
   
@@ -1317,7 +1344,7 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context,
          else
            {
              /* invalid hex sequence */
-             gdk_display_beep (gdk_drawable_get_display (event->window));
+             beep_window (event->window);
              
              context_simple->tentative_match = 0;
              context_simple->in_hex_sequence = FALSE;
@@ -1403,7 +1430,7 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context,
        {
          /* invalid hex sequence */
          if (n_compose > 0)
-           gdk_display_beep (gdk_drawable_get_display (event->window));
+           beep_window (event->window);
          
          context_simple->tentative_match = 0;
          context_simple->in_hex_sequence = FALSE;
@@ -1438,7 +1465,7 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context,
       else if (!is_hex_end)
        {
          /* non-hex character in hex sequence */
-         gdk_display_beep (gdk_drawable_get_display (event->window));
+         beep_window (event->window);
          
          return TRUE;
        }
@@ -1465,7 +1492,7 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context,
              else
                {
                  /* invalid hex sequence */
-                 gdk_display_beep (gdk_drawable_get_display (event->window));
+                 beep_window (event->window);
 
                  context_simple->tentative_match = 0;
                  context_simple->in_hex_sequence = FALSE;
@@ -1473,7 +1500,7 @@ gtk_im_context_simple_filter_keypress (GtkIMContext *context,
                }
             }
           else if (!check_hex (context_simple, n_compose))
-           gdk_display_beep (gdk_drawable_get_display (event->window));
+           beep_window (event->window);
          
          g_signal_emit_by_name (context_simple, "preedit_changed");
 
index 409bc18e53f819e62f4f1cc9095f079dbb2a9db9..cfe21cef713f0b590e6ed076b084185fd6742514 100644 (file)
@@ -891,8 +891,8 @@ gtk_label_mnemonic_activate (GtkWidget *widget,
 
   /* barf if there was nothing to activate */
   g_warning ("Couldn't find a target for a mnemonic activation.");
-  gdk_display_beep (gtk_widget_get_display (widget));
-  
+  gtk_widget_error_bell (widget);
+
   return FALSE;
 }
 
@@ -3871,12 +3871,13 @@ gtk_label_move_cursor (GtkLabel       *label,
                       gint            count,
                       gboolean        extend_selection)
 {
+  gint old_pos;
   gint new_pos;
   
   if (label->select_info == NULL)
     return;
-  
-  new_pos = label->select_info->selection_end;
+
+  old_pos = new_pos = label->select_info->selection_end;
 
   if (label->select_info->selection_end != label->select_info->selection_anchor &&
       !extend_selection)
@@ -3901,7 +3902,6 @@ gtk_label_move_cursor (GtkLabel       *label,
              new_pos = end_is_left ? label->select_info->selection_end : label->select_info->selection_anchor;
            else
              new_pos = !end_is_left ? label->select_info->selection_end : label->select_info->selection_anchor;
-
            break;
          }
        case GTK_MOVEMENT_LOGICAL_POSITIONS:
@@ -3933,6 +3933,27 @@ gtk_label_move_cursor (GtkLabel       *label,
          break;
        case GTK_MOVEMENT_VISUAL_POSITIONS:
          new_pos = gtk_label_move_visually (label, new_pos, count);
+          if (new_pos == old_pos)
+            {
+              if (!extend_selection)
+                {
+                  if (!gtk_widget_keynav_failed (GTK_WIDGET (label),
+                                                 count > 0 ?
+                                                 GTK_DIR_RIGHT : GTK_DIR_LEFT))
+                    {
+                      GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (label));
+
+                      if (toplevel)
+                        gtk_widget_child_focus (toplevel,
+                                                count > 0 ?
+                                                GTK_DIR_RIGHT : GTK_DIR_LEFT);
+                    }
+                }
+              else
+                {
+                  gtk_widget_error_bell (GTK_WIDGET (label));
+                }
+            }
          break;
        case GTK_MOVEMENT_WORDS:
          while (count > 0)
@@ -3945,12 +3966,16 @@ gtk_label_move_cursor (GtkLabel       *label,
              new_pos = gtk_label_move_backward_word (label, new_pos);
              count++;
            }
+          if (new_pos == old_pos)
+            gtk_widget_error_bell (GTK_WIDGET (label));
          break;
        case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
        case GTK_MOVEMENT_PARAGRAPH_ENDS:
        case GTK_MOVEMENT_BUFFER_ENDS:
          /* FIXME: Can do better here */
          new_pos = count < 0 ? 0 : strlen (label->text);
+          if (new_pos == old_pos)
+            gtk_widget_error_bell (GTK_WIDGET (label));
          break;
        case GTK_MOVEMENT_DISPLAY_LINES:
        case GTK_MOVEMENT_PARAGRAPHS:
index b44e756e99d18868bbd8a8f032792a6594bfdb31..be89918fce2b71c6e2cba94fbe33f31849d99f18 100644 (file)
@@ -2756,7 +2756,7 @@ gtk_menu_key_press (GtkWidget     *widget,
           * (basically, those items are accelerator-locked).
           */
          /* g_print("item has no path or is locked, menu prefix: %s\n", menu->accel_path); */
-         gdk_display_beep (display);
+         gtk_widget_error_bell (widget);
        }
       else
        {
@@ -2785,7 +2785,7 @@ gtk_menu_key_press (GtkWidget     *widget,
               * locked already
               */
              /* g_print("failed to change\n"); */
-             gdk_display_beep (display);
+             gtk_widget_error_bell (widget);
            }
        }
     }
index cb43ce1927f70710695cefcc4fd0b7bb7d36e653..153620af39eb04e0fcaba83fb3a422fe05826d4a 100644 (file)
@@ -1044,17 +1044,27 @@ gtk_menu_shell_move_selected (GtkMenuShell  *menu_shell,
       GList *node = g_list_find (menu_shell->children,
                                 menu_shell->active_menu_item);
       GList *start_node = node;
-      
+      gboolean wrap_around;
+
+      g_object_get (gtk_widget_get_settings (GTK_WIDGET (menu_shell)),
+                    "gtk-keynav-wrap-around", &wrap_around,
+                    NULL);
+
       if (distance > 0)
        {
          node = node->next;
          while (node != start_node && 
                 (!node || !_gtk_menu_item_is_selectable (node->data)))
            {
-             if (!node)
-               node = menu_shell->children;
-             else
+             if (node)
                node = node->next;
+              else if (wrap_around)
+               node = menu_shell->children;
+              else
+                {
+                  gtk_widget_error_bell (GTK_WIDGET (menu_shell));
+                  break;
+                }
            }
        }
       else
@@ -1063,10 +1073,15 @@ gtk_menu_shell_move_selected (GtkMenuShell  *menu_shell,
          while (node != start_node &&
                 (!node || !_gtk_menu_item_is_selectable (node->data)))
            {
-             if (!node)
-               node = g_list_last (menu_shell->children);
-             else
+             if (node)
                node = node->prev;
+              else if (wrap_around)
+               node = g_list_last (menu_shell->children);
+              else
+                {
+                  gtk_widget_error_bell (GTK_WIDGET (menu_shell));
+                  break;
+                }
            }
        }
       
index cd52f6fb286d9d3b9b8d729130d603471efbf71a..19a584c6185dc9c624a6a6ca8c177814d43d18ab 100644 (file)
@@ -1066,14 +1066,33 @@ gtk_notebook_change_current_page (GtkNotebook *notebook,
 
   while (offset != 0)
     {
-      current = gtk_notebook_search_page (notebook, current, offset < 0 ? STEP_PREV : STEP_NEXT, TRUE);
+      current = gtk_notebook_search_page (notebook, current,
+                                          offset < 0 ? STEP_PREV : STEP_NEXT,
+                                          TRUE);
+
+      if (!current)
+        {
+          gboolean wrap_around;
+
+          g_object_get (gtk_widget_get_settings (GTK_WIDGET (notebook)),
+                        "gtk-keynav-wrap-around", &wrap_around,
+                        NULL);
+
+          if (wrap_around)
+            current = gtk_notebook_search_page (notebook, NULL,
+                                                offset < 0 ? STEP_PREV : STEP_NEXT,
+                                                TRUE);
+          else
+            break;
+        }
+
       offset += offset < 0 ? 1 : -1;
     }
 
   if (current)
     gtk_notebook_switch_page (notebook, current->data, -1);
   else
-    gdk_display_beep (gtk_widget_get_display (GTK_WIDGET (notebook)));
+    gtk_widget_error_bell (GTK_WIDGET (notebook));
 }
 
 static GtkDirectionType
@@ -3589,11 +3608,24 @@ focus_tabs_move (GtkNotebook     *notebook,
 
   new_page = gtk_notebook_search_page (notebook, notebook->focus_tab,
                                       search_direction, TRUE);
+  if (!new_page)
+    {
+      gboolean wrap_around;
+
+      g_object_get (gtk_widget_get_settings (GTK_WIDGET (notebook)),
+                    "gtk-keynav-wrap-around", &wrap_around,
+                    NULL);
+
+      if (wrap_around)
+        new_page = gtk_notebook_search_page (notebook, NULL,
+                                             search_direction, TRUE);
+    }
+
   if (new_page)
     gtk_notebook_switch_focus_tab (notebook, new_page);
   else
-    gdk_display_beep (gtk_widget_get_display (GTK_WIDGET (notebook)));
-  
+    gtk_widget_error_bell (GTK_WIDGET (notebook));
+
   return TRUE;
 }
 
index d1d7f4fe931462e30a557a4057d1e07eece6d0d6..91bedc3f8178bad9b40bd208d2d67412af60ea7e 100644 (file)
@@ -491,6 +491,12 @@ gtk_radio_button_focus (GtkWidget         *widget,
 
       if (!new_focus)
        {
+          if (!gtk_widget_keynav_failed (widget, direction))
+            {
+              g_slist_free (focus_list);
+              return FALSE;
+            }
+
          tmp_list = focus_list;
 
          while (tmp_list)
@@ -511,8 +517,17 @@ gtk_radio_button_focus (GtkWidget         *widget,
 
       if (new_focus)
        {
+          GtkSettings *settings = gtk_widget_get_settings (widget);
+          gboolean     cursor_only;
+
+          g_object_get (settings,
+                        "gtk-keynav-cursor-only", &cursor_only,
+                        NULL);
+
          gtk_widget_grab_focus (new_focus);
-         gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (new_focus), TRUE);
+
+          if (!cursor_only)
+            gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (new_focus), TRUE);
        }
 
       return TRUE;
index 216f9049c8953f76b8926109d33ae59853664491..c6c6e56ddafa18c48349987ee7e2b39b3da71ae3 100644 (file)
@@ -2457,6 +2457,42 @@ static void
 gtk_range_move_slider (GtkRange     *range,
                        GtkScrollType scroll)
 {
+  gboolean cursor_only;
+
+  g_object_get (gtk_widget_get_settings (GTK_WIDGET (range)),
+                "gtk-keynav-cursor-only", &cursor_only,
+                NULL);
+
+  if (cursor_only)
+    {
+      GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (range));
+
+      if (range->orientation == GTK_ORIENTATION_HORIZONTAL)
+        {
+          if (scroll == GTK_SCROLL_STEP_UP ||
+              scroll == GTK_SCROLL_STEP_DOWN)
+            {
+              if (toplevel)
+                gtk_widget_child_focus (toplevel,
+                                        scroll == GTK_SCROLL_STEP_UP ?
+                                        GTK_DIR_UP : GTK_DIR_DOWN);
+              return;
+            }
+        }
+      else
+        {
+          if (scroll == GTK_SCROLL_STEP_LEFT ||
+              scroll == GTK_SCROLL_STEP_RIGHT)
+            {
+              if (toplevel)
+                gtk_widget_child_focus (toplevel,
+                                        scroll == GTK_SCROLL_STEP_LEFT ?
+                                        GTK_DIR_LEFT : GTK_DIR_RIGHT);
+              return;
+            }
+        }
+    }
+
   gtk_range_scroll (range, scroll);
 
   /* Policy DELAYED makes sense with key events,
index 0054a2fcb4c79f3c7902a0845f674d08fdbd7574..cd3eda5900f56f6f02964592552b4de633a56493 100644 (file)
@@ -93,6 +93,9 @@ enum {
   PROP_COLOR_SCHEME,
   PROP_ENABLE_ANIMATIONS,
   PROP_TOUCHSCREEN_MODE,
+  PROP_KEYNAV_CURSOR_ONLY,
+  PROP_KEYNAV_WRAP_AROUND,
+  PROP_ERROR_BELL,
   PROP_COLOR_HASH
 };
 
@@ -507,7 +510,7 @@ gtk_settings_class_init (GtkSettingsClass *class)
   /**
    * GtkSettings:gtk-touchscreen-mode:
    *
-   * When TRUE, there are no motion notify events delivered on this screen,
+   * When %TRUE, there are no motion notify events delivered on this screen,
    * and widgets can't use the pointer hovering them for any essential
    * functionality.
    *
@@ -523,6 +526,64 @@ gtk_settings_class_init (GtkSettingsClass *class)
 
   g_assert (result == PROP_TOUCHSCREEN_MODE);
 
+  /**
+   * GtkSettings:gtk-keynav-cursor-only:
+   *
+   * When %TRUE, keyboard navigation should be able to reach all widgets
+   * by using the cursor keys only. Tab, Shift etc. keys can't be expected
+   * to be present on the used input device.
+   *
+   * Since: 2.12
+   */
+  result = settings_install_property_parser (class,
+                                             g_param_spec_boolean ("gtk-keynav-cursor-only",
+                                                                   P_("Keynav Cursor Only"),
+                                                                   P_("When TRUE, there are only cursor keys available to navigate widgets"),
+                                                                   FALSE,
+                                                                   GTK_PARAM_READWRITE),
+                                             NULL);
+
+  g_assert (result == PROP_KEYNAV_CURSOR_ONLY);
+
+  /**
+   * GtkSettings:gtk-keynav-wrap-around:
+   *
+   * When %TRUE, some widgets will wrap around when doing keyboard
+   * navigation, such as menus, menubars and notebooks.
+   *
+   * Since: 2.12
+   */
+  result = settings_install_property_parser (class,
+                                             g_param_spec_boolean ("gtk-keynav-wrap-around",
+                                                                   P_("Keynav Wrap Around"),
+                                                                   P_("Whether to wrap around when keyboard-navigating widgets"),
+                                                                   TRUE,
+                                                                   GTK_PARAM_READWRITE),
+                                             NULL);
+
+  g_assert (result == PROP_KEYNAV_WRAP_AROUND);
+
+  /**
+   * GtkSettings:gtk-error-bell:
+   *
+   * When %TRUE, keyboard navigation and other input-related errors
+   * will cause a beep. Since the error bell is implemented using
+   * gdk_window_beep(), the windowing system may offer ways to
+   * configure the error bell in many ways, such as flashing the
+   * window or similar visual effects.
+   *
+   * Since: 2.12
+   */
+  result = settings_install_property_parser (class,
+                                             g_param_spec_boolean ("gtk-error-bell",
+                                                                   P_("Error Bell"),
+                                                                   P_("When TRUE, keyboard navigation and other errors will cause a beep"),
+                                                                   TRUE,
+                                                                   GTK_PARAM_READWRITE),
+                                             NULL);
+
+  g_assert (result == PROP_ERROR_BELL);
+
   /**
    * GtkSettings:color-hash:
    *
index 7c76829cb62e89a60c5c66ada7871ca745a86c76..bcd360bbc4918cc2ac47d1d8653c3c80e0ae254c 100644 (file)
@@ -1243,6 +1243,8 @@ static void
 gtk_spin_button_real_change_value (GtkSpinButton *spin,
                                   GtkScrollType  scroll)
 {
+  gdouble old_value = spin->adjustment->value;
+
   /* We don't test whether the entry is editable, since
    * this key binding conceptually corresponds to changing
    * the value with the buttons using the mouse, which
@@ -1320,6 +1322,9 @@ gtk_spin_button_real_change_value (GtkSpinButton *spin,
     }
   
   gtk_spin_button_update (spin);
+
+  if (spin->adjustment->value == old_value)
+    gtk_widget_error_bell (GTK_WIDGET (spin));
 }
 
 static gint
index 69eb696222fba8e71d3cb0cfa00cde7f833a2069..d3e5b23711311b0c16a558535266c1eced255d56 100644 (file)
@@ -260,10 +260,10 @@ static void gtk_text_view_move_viewport     (GtkTextView           *text_view,
                                              GtkScrollStep          step,
                                              gint                   count);
 static void gtk_text_view_set_anchor       (GtkTextView           *text_view);
-static void gtk_text_view_scroll_pages     (GtkTextView           *text_view,
+static gboolean gtk_text_view_scroll_pages (GtkTextView           *text_view,
                                             gint                   count,
                                             gboolean               extend_selection);
-static void gtk_text_view_scroll_hpages    (GtkTextView           *text_view,
+static gboolean gtk_text_view_scroll_hpages(GtkTextView           *text_view,
                                             gint                   count,
                                             gboolean               extend_selection);
 static void gtk_text_view_insert_at_cursor (GtkTextView           *text_view,
@@ -3922,6 +3922,9 @@ gtk_text_view_key_press_event (GtkWidget *widget, GdkEventKey *event)
   gtk_text_view_reset_blink_time (text_view);
   gtk_text_view_pend_cursor_blink (text_view);
 
+  if (!retval && event->length)
+    gtk_widget_error_bell (widget);
+
   return retval;
 }
 
@@ -4688,8 +4691,8 @@ gtk_text_view_move_cursor_internal (GtkTextView     *text_view,
 {
   GtkTextIter insert;
   GtkTextIter newplace;
-
   gint cursor_x_pos = 0;
+  GtkDirectionType leave_direction = -1;
 
   if (!text_view->cursor_visible) 
     {
@@ -4733,14 +4736,18 @@ gtk_text_view_move_cursor_internal (GtkTextView     *text_view,
 
   if (step == GTK_MOVEMENT_PAGES)
     {
-      gtk_text_view_scroll_pages (text_view, count, extend_selection);
+      if (!gtk_text_view_scroll_pages (text_view, count, extend_selection))
+        gtk_widget_error_bell (GTK_WIDGET (text_view));
+
       gtk_text_view_check_cursor_blink (text_view);
       gtk_text_view_pend_cursor_blink (text_view);
       return;
     }
   else if (step == GTK_MOVEMENT_HORIZONTAL_PAGES)
     {
-      gtk_text_view_scroll_hpages (text_view, count, extend_selection);
+      if (!gtk_text_view_scroll_hpages (text_view, count, extend_selection))
+        gtk_widget_error_bell (GTK_WIDGET (text_view));
+
       gtk_text_view_check_cursor_blink (text_view);
       gtk_text_view_pend_cursor_blink (text_view);
       return;
@@ -4777,19 +4784,23 @@ gtk_text_view_move_cursor_internal (GtkTextView     *text_view,
 
     case GTK_MOVEMENT_DISPLAY_LINES:
       if (count < 0)
-      {
-        if (gtk_text_view_move_iter_by_lines (text_view, &newplace, count))
-          gtk_text_layout_move_iter_to_x (text_view->layout, &newplace, cursor_x_pos);
-        else
-          gtk_text_iter_set_line_offset (&newplace, 0);
-      }
+        {
+          leave_direction = GTK_DIR_UP;
+
+          if (gtk_text_view_move_iter_by_lines (text_view, &newplace, count))
+            gtk_text_layout_move_iter_to_x (text_view->layout, &newplace, cursor_x_pos);
+          else
+            gtk_text_iter_set_line_offset (&newplace, 0);
+        }
       if (count > 0)
-      {
-        if (gtk_text_view_move_iter_by_lines (text_view, &newplace, count))
-          gtk_text_layout_move_iter_to_x (text_view->layout, &newplace, cursor_x_pos);
-        else
-          gtk_text_iter_forward_to_line_end (&newplace);
-      }
+        {
+          leave_direction = GTK_DIR_DOWN;
+
+          if (gtk_text_view_move_iter_by_lines (text_view, &newplace, count))
+            gtk_text_layout_move_iter_to_x (text_view->layout, &newplace, cursor_x_pos);
+          else
+            gtk_text_iter_forward_to_line_end (&newplace);
+        }
       break;
 
     case GTK_MOVEMENT_DISPLAY_LINE_ENDS:
@@ -4860,6 +4871,18 @@ gtk_text_view_move_cursor_internal (GtkTextView     *text_view,
       if (step == GTK_MOVEMENT_DISPLAY_LINES)
         gtk_text_view_set_virtual_cursor_pos (text_view, cursor_x_pos, -1);
     }
+  else if (leave_direction != -1)
+    {
+      if (!gtk_widget_keynav_failed (GTK_WIDGET (text_view),
+                                     leave_direction))
+        {
+          gtk_text_view_move_focus (text_view, leave_direction);
+        }
+    }
+  else
+    {
+      gtk_widget_error_bell (GTK_WIDGET (text_view));
+    }
 
   gtk_text_view_check_cursor_blink (text_view);
   gtk_text_view_pend_cursor_blink (text_view);
@@ -4943,7 +4966,7 @@ gtk_text_view_set_anchor (GtkTextView *text_view)
   gtk_text_buffer_create_mark (get_buffer (text_view), "anchor", &insert, TRUE);
 }
 
-static void
+static gboolean
 gtk_text_view_scroll_pages (GtkTextView *text_view,
                             gint         count,
                             gboolean     extend_selection)
@@ -4952,11 +4975,12 @@ gtk_text_view_scroll_pages (GtkTextView *text_view,
   gdouble oldval;
   GtkAdjustment *adj;
   gint cursor_x_pos, cursor_y_pos;
+  GtkTextIter old_insert;
   GtkTextIter new_insert;
   GtkTextIter anchor;
   gint y0, y1;
 
-  g_return_if_fail (text_view->vadjustment != NULL);
+  g_return_val_if_fail (text_view->vadjustment != NULL, FALSE);
   
   adj = text_view->vadjustment;
 
@@ -4970,9 +4994,13 @@ gtk_text_view_scroll_pages (GtkTextView *text_view,
     gtk_text_view_scroll_mark_onscreen (text_view,
                                        gtk_text_buffer_get_mark (get_buffer (text_view),
                                                                  "insert"));
-  
-/* Validate the region that will be brought into view by the cursor motion
+
+  /* Validate the region that will be brought into view by the cursor motion
    */
+  gtk_text_buffer_get_iter_at_mark (get_buffer (text_view),
+                                    &old_insert,
+                                    gtk_text_buffer_get_mark (get_buffer (text_view), "insert"));
+
   if (count < 0)
     {
       gtk_text_view_get_first_para_iter (text_view, &anchor);
@@ -4989,6 +5017,8 @@ gtk_text_view_scroll_pages (GtkTextView *text_view,
   gtk_text_layout_validate_yrange (text_view->layout, &anchor, y0, y1);
   /* FIXME do we need to update the adjustment ranges here? */
 
+  new_insert = old_insert;
+
   if (count < 0 && adj->value <= (adj->lower + 1e-12))
     {
       /* already at top, just be sure we are at offset 0 */
@@ -5005,9 +5035,9 @@ gtk_text_view_scroll_pages (GtkTextView *text_view,
     {
       gtk_text_view_get_virtual_cursor_pos (text_view, &cursor_x_pos, &cursor_y_pos);
 
-      newval = adj->value;
       oldval = adj->value;
-  
+      newval = adj->value;
+
       newval += count * adj->page_increment;
 
       set_adjustment_clamped (adj, newval);
@@ -5027,9 +5057,11 @@ gtk_text_view_scroll_pages (GtkTextView *text_view,
   gtk_text_view_scroll_mark_onscreen (text_view,
                                       gtk_text_buffer_get_mark (get_buffer (text_view),
                                                                 "insert"));
+
+  return !gtk_text_iter_equal (&old_insert, &new_insert);
 }
 
-static void
+static gboolean
 gtk_text_view_scroll_hpages (GtkTextView *text_view,
                              gint         count,
                              gboolean     extend_selection)
@@ -5038,10 +5070,11 @@ gtk_text_view_scroll_hpages (GtkTextView *text_view,
   gdouble oldval;
   GtkAdjustment *adj;
   gint cursor_x_pos, cursor_y_pos;
+  GtkTextIter old_insert;
   GtkTextIter new_insert;
   gint y, height;
   
-  g_return_if_fail (text_view->hadjustment != NULL);
+  g_return_val_if_fail (text_view->hadjustment != NULL, FALSE);
 
   adj = text_view->hadjustment;
 
@@ -5055,16 +5088,18 @@ gtk_text_view_scroll_hpages (GtkTextView *text_view,
     gtk_text_view_scroll_mark_onscreen (text_view,
                                        gtk_text_buffer_get_mark (get_buffer (text_view),
                                                                  "insert"));
-  
+
   /* Validate the line that we're moving within.
    */
   gtk_text_buffer_get_iter_at_mark (get_buffer (text_view),
-                                    &new_insert,
+                                    &old_insert,
                                     gtk_text_buffer_get_mark (get_buffer (text_view), "insert"));
   gtk_text_layout_get_line_yrange (text_view->layout, &new_insert, &y, &height);
   gtk_text_layout_validate_yrange (text_view->layout, &new_insert, y, y + height);
   /* FIXME do we need to update the adjustment ranges here? */
-  
+
+  new_insert = old_insert;
+
   if (count < 0 && adj->value <= (adj->lower + 1e-12))
     {
       /* already at far left, just be sure we are at offset 0 */
@@ -5082,9 +5117,9 @@ gtk_text_view_scroll_hpages (GtkTextView *text_view,
     {
       gtk_text_view_get_virtual_cursor_pos (text_view, &cursor_x_pos, &cursor_y_pos);
 
-      newval = adj->value;
       oldval = adj->value;
-  
+      newval = adj->value;
+
       newval += count * adj->page_increment;
 
       set_adjustment_clamped (adj, newval);
@@ -5110,6 +5145,8 @@ gtk_text_view_scroll_hpages (GtkTextView *text_view,
   gtk_text_view_scroll_mark_onscreen (text_view,
                                       gtk_text_buffer_get_mark (get_buffer (text_view),
                                                                 "insert"));
+
+  return !gtk_text_iter_equal (&old_insert, &new_insert);
 }
 
 static gboolean
@@ -5143,8 +5180,11 @@ static void
 gtk_text_view_insert_at_cursor (GtkTextView *text_view,
                                 const gchar *str)
 {
-  gtk_text_buffer_insert_interactive_at_cursor (get_buffer (text_view), str, -1,
-                                                text_view->editable);
+  if (!gtk_text_buffer_insert_interactive_at_cursor (get_buffer (text_view), str, -1,
+                                                     text_view->editable))
+    {
+      gtk_widget_error_bell (GTK_WIDGET (text_view));
+    }
 }
 
 static void
@@ -5261,6 +5301,10 @@ gtk_text_view_delete_from_cursor (GtkTextView   *text_view,
                                                           " ", 1,
                                                           text_view->editable);
         }
+      else
+        {
+          gtk_widget_error_bell (GTK_WIDGET (text_view));
+        }
 
       gtk_text_buffer_end_user_action (get_buffer (text_view));
       gtk_text_view_set_virtual_cursor_pos (text_view, -1, -1);
@@ -5269,6 +5313,10 @@ gtk_text_view_delete_from_cursor (GtkTextView   *text_view,
       gtk_text_view_scroll_mark_onscreen (text_view,
                                           gtk_text_buffer_get_mark (get_buffer (text_view), "insert"));
     }
+  else
+    {
+      gtk_widget_error_bell (GTK_WIDGET (text_view));
+    }
 }
 
 static void
@@ -5295,6 +5343,10 @@ gtk_text_view_backspace (GtkTextView *text_view)
       gtk_text_view_scroll_mark_onscreen (text_view,
                                           gtk_text_buffer_get_insert (get_buffer (text_view)));
     }
+  else
+    {
+      gtk_widget_error_bell (GTK_WIDGET (text_view));
+    }
 }
 
 static void
@@ -6337,9 +6389,13 @@ insert_text_data (GtkTextView      *text_view,
 
   if (str)
     {
-      gtk_text_buffer_insert_interactive (get_buffer (text_view),
-                                          drop_point, (gchar *) str, -1,
-                                          text_view->editable);
+      if (!gtk_text_buffer_insert_interactive (get_buffer (text_view),
+                                               drop_point, (gchar *) str, -1,
+                                               text_view->editable))
+        {
+          gtk_widget_error_bell (GTK_WIDGET (text_view));
+        }
+
       g_free (str);
     }
 }
@@ -6804,8 +6860,11 @@ gtk_text_view_commit_text (GtkTextView   *text_view,
 
   if (!strcmp (str, "\n"))
     {
-      gtk_text_buffer_insert_interactive_at_cursor (get_buffer (text_view), "\n", 1,
-                                                    text_view->editable);
+      if (!gtk_text_buffer_insert_interactive_at_cursor (get_buffer (text_view), "\n", 1,
+                                                         text_view->editable))
+        {
+          gtk_widget_error_bell (GTK_WIDGET (text_view));
+        }
     }
   else
     {
@@ -6820,8 +6879,12 @@ gtk_text_view_commit_text (GtkTextView   *text_view,
          if (!gtk_text_iter_ends_line (&insert))
            gtk_text_view_delete_from_cursor (text_view, GTK_DELETE_CHARS, 1);
        }
-      gtk_text_buffer_insert_interactive_at_cursor (get_buffer (text_view), str, -1,
-                                                    text_view->editable);
+
+      if (!gtk_text_buffer_insert_interactive_at_cursor (get_buffer (text_view), str, -1,
+                                                         text_view->editable))
+        {
+          gtk_widget_error_bell (GTK_WIDGET (text_view));
+        }
     }
 
   gtk_text_buffer_end_user_action (get_buffer (text_view));
index 2f6a56e405d72f782df844a0d4065455da281180..e8190d1669ab59adac8f67a874f53b1570f37646 100644 (file)
@@ -413,7 +413,7 @@ static gboolean gtk_tree_view_search_scroll_event       (GtkWidget        *entry
 static gboolean gtk_tree_view_search_key_press_event    (GtkWidget        *entry,
                                                         GdkEventKey      *event,
                                                         GtkTreeView      *tree_view);
-static void     gtk_tree_view_search_move               (GtkWidget        *window,
+static gboolean gtk_tree_view_search_move               (GtkWidget        *window,
                                                         GtkTreeView      *tree_view,
                                                         gboolean          up);
 static gboolean gtk_tree_view_search_equal_func         (GtkTreeModel     *model,
@@ -5115,11 +5115,16 @@ gtk_tree_view_key_press (GtkWidget   *widget,
           GtkTreeViewColumn *column = GTK_TREE_VIEW_COLUMN (focus_column->data);
 
           if (!column->resizable)
-            return TRUE;
+            {
+              gtk_widget_error_bell (widget);
+              return TRUE;
+            }
 
           if (event->keyval == (rtl ? GDK_Right : GDK_Left)
               || event->keyval == (rtl ? GDK_KP_Right : GDK_KP_Left))
             {
+              gint old_width = column->resized_width;
+
               column->resized_width = MAX (column->resized_width,
                                            column->width);
               column->resized_width -= 2;
@@ -5138,11 +5143,17 @@ gtk_tree_view_key_press (GtkWidget   *widget,
                                              column->max_width);
 
               column->use_resized_width = TRUE;
-              gtk_widget_queue_resize (widget);
+
+              if (column->resized_width != old_width)
+                gtk_widget_queue_resize (widget);
+              else
+                gtk_widget_error_bell (widget);
             }
           else if (event->keyval == (rtl ? GDK_Left : GDK_Right)
                    || event->keyval == (rtl ? GDK_KP_Left : GDK_KP_Right))
             {
+              gint old_width = column->resized_width;
+
               column->resized_width = MAX (column->resized_width,
                                            column->width);
               column->resized_width += 2;
@@ -5152,7 +5163,11 @@ gtk_tree_view_key_press (GtkWidget   *widget,
                                              column->max_width);
 
               column->use_resized_width = TRUE;
-              gtk_widget_queue_resize (widget);
+
+              if (column->resized_width != old_width)
+                gtk_widget_queue_resize (widget);
+              else
+                gtk_widget_error_bell (widget);
             }
 
           return TRUE;
@@ -5174,6 +5189,8 @@ gtk_tree_view_key_press (GtkWidget   *widget,
               col = gtk_tree_view_get_drop_column (tree_view, column, DROP_LEFT);
               if (col != (GtkTreeViewColumn *)0x1)
                 gtk_tree_view_move_column_after (tree_view, column, col);
+              else
+                gtk_widget_error_bell (widget);
             }
           else if (event->keyval == (rtl ? GDK_Left : GDK_Right)
                    || event->keyval == (rtl ? GDK_KP_Left : GDK_KP_Right))
@@ -5182,6 +5199,8 @@ gtk_tree_view_key_press (GtkWidget   *widget,
               col = gtk_tree_view_get_drop_column (tree_view, column, DROP_RIGHT);
               if (col != (GtkTreeViewColumn *)0x1)
                 gtk_tree_view_move_column_after (tree_view, column, col);
+              else
+                gtk_widget_error_bell (widget);
             }
           else if (event->keyval == GDK_Home || event->keyval == GDK_KP_Home)
             {
@@ -5189,6 +5208,8 @@ gtk_tree_view_key_press (GtkWidget   *widget,
               col = gtk_tree_view_get_drop_column (tree_view, column, DROP_HOME);
               if (col != (GtkTreeViewColumn *)0x1)
                 gtk_tree_view_move_column_after (tree_view, column, col);
+              else
+                gtk_widget_error_bell (widget);
             }
           else if (event->keyval == GDK_End || event->keyval == GDK_KP_End)
             {
@@ -5196,6 +5217,8 @@ gtk_tree_view_key_press (GtkWidget   *widget,
               col = gtk_tree_view_get_drop_column (tree_view, column, DROP_END);
               if (col != (GtkTreeViewColumn *)0x1)
                 gtk_tree_view_move_column_after (tree_view, column, col);
+              else
+                gtk_widget_error_bell (widget);
             }
 
           return TRUE;
@@ -5206,8 +5229,7 @@ gtk_tree_view_key_press (GtkWidget   *widget,
            || event->keyval == GDK_Right || event->keyval == GDK_KP_Right))
         {
           if ((event->keyval == (rtl ? GDK_Right : GDK_Left)
-               || event->keyval == (rtl ? GDK_KP_Right : GDK_KP_Left))
-              && focus_column->prev)
+               || event->keyval == (rtl ? GDK_KP_Right : GDK_KP_Left)))
             {
               GList *tmp;
 
@@ -5216,7 +5238,10 @@ gtk_tree_view_key_press (GtkWidget   *widget,
                   break;
 
               if (!tmp)
-                return FALSE;
+                {
+                  gtk_widget_error_bell (widget);
+                  return TRUE;
+                }
 
               tree_view->priv->focus_column = GTK_TREE_VIEW_COLUMN (tmp->data);
               gtk_widget_grab_focus (tree_view->priv->focus_column->button);
@@ -5227,8 +5252,7 @@ gtk_tree_view_key_press (GtkWidget   *widget,
                                                tree_view->priv->hadjustment->upper - tree_view->priv->hadjustment->page_size));
             }
           else if ((event->keyval == (rtl ? GDK_Left : GDK_Right)
-                    || event->keyval == (rtl ? GDK_KP_Left : GDK_KP_Right))
-                   && focus_column->next)
+                    || event->keyval == (rtl ? GDK_KP_Left : GDK_KP_Right)))
             {
               GList *tmp;
 
@@ -5237,7 +5261,10 @@ gtk_tree_view_key_press (GtkWidget   *widget,
                   break;
 
               if (!tmp)
-                return FALSE;
+                {
+                  gtk_widget_error_bell (widget);
+                  return TRUE;
+                }
 
               tree_view->priv->focus_column = GTK_TREE_VIEW_COLUMN (tmp->data);
 
@@ -9558,6 +9585,7 @@ gtk_tree_view_move_cursor_up_down (GtkTreeView *tree_view,
   else
     {
       gtk_tree_view_clamp_node_visible (tree_view, cursor_tree, cursor_node);
+      gtk_widget_error_bell (GTK_WIDGET (tree_view));
     }
 
   gtk_widget_grab_focus (GTK_WIDGET (tree_view));
@@ -9569,6 +9597,7 @@ gtk_tree_view_move_cursor_page_up_down (GtkTreeView *tree_view,
 {
   GtkRBTree *cursor_tree = NULL;
   GtkRBNode *cursor_node = NULL;
+  GtkTreePath *old_cursor_path = NULL;
   GtkTreePath *cursor_path = NULL;
   gint y;
   gint window_y;
@@ -9578,20 +9607,21 @@ gtk_tree_view_move_cursor_page_up_down (GtkTreeView *tree_view,
     return;
 
   if (gtk_tree_row_reference_valid (tree_view->priv->cursor))
-    cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
+    old_cursor_path = gtk_tree_row_reference_get_path (tree_view->priv->cursor);
   else
     /* This is sorta weird.  Focus in should give us a cursor */
     return;
 
   gtk_widget_style_get (GTK_WIDGET (tree_view), "vertical-separator", &vertical_separator, NULL);
-  _gtk_tree_view_find_node (tree_view, cursor_path,
+  _gtk_tree_view_find_node (tree_view, old_cursor_path,
                            &cursor_tree, &cursor_node);
 
-  gtk_tree_path_free (cursor_path);
-
   if (cursor_tree == NULL)
-    /* FIXME: we lost the cursor.  Should we try to get one? */
-    return;
+    {
+      /* FIXME: we lost the cursor.  Should we try to get one? */
+      gtk_tree_path_free (old_cursor_path);
+      return;
+    }
   g_return_if_fail (cursor_node != NULL);
 
   y = _gtk_rbtree_node_find_offset (cursor_tree, cursor_node);
@@ -9606,11 +9636,16 @@ gtk_tree_view_move_cursor_page_up_down (GtkTreeView *tree_view,
   cursor_path = _gtk_tree_view_find_path (tree_view, cursor_tree, cursor_node);
   g_return_if_fail (cursor_path != NULL);
   gtk_tree_view_real_set_cursor (tree_view, cursor_path, TRUE, FALSE);
-  gtk_tree_path_free (cursor_path);
 
   y -= window_y;
   gtk_tree_view_scroll_to_point (tree_view, -1, y);
   _gtk_tree_view_queue_draw_node (tree_view, cursor_tree, cursor_node, NULL);
+
+  if (!gtk_tree_path_compare (old_cursor_path, cursor_path))
+    gtk_widget_error_bell (GTK_WIDGET (tree_view));
+
+  gtk_tree_path_free (old_cursor_path);
+  gtk_tree_path_free (cursor_path);
 }
 
 static void
@@ -9703,6 +9738,11 @@ gtk_tree_view_move_cursor_left_right (GtkTreeView *tree_view,
                                        NULL);
       g_signal_emit (tree_view, tree_view_signals[CURSOR_CHANGED], 0);
     }
+  else
+    {
+      gtk_widget_error_bell (GTK_WIDGET (tree_view));
+    }
+
   gtk_tree_view_clamp_column_visible (tree_view, tree_view->priv->focus_column);
 }
 
@@ -9713,23 +9753,25 @@ gtk_tree_view_move_cursor_start_end (GtkTreeView *tree_view,
   GtkRBTree *cursor_tree;
   GtkRBNode *cursor_node;
   GtkTreePath *path;
+  GtkTreePath *old_path;
 
   if (! GTK_WIDGET_HAS_FOCUS (tree_view))
     return;
 
   g_return_if_fail (tree_view->priv->tree != NULL);
 
+  gtk_tree_view_get_cursor (tree_view, &old_path, NULL);
+
+  cursor_tree = tree_view->priv->tree;
+  cursor_node = cursor_tree->root;
+
   if (count == -1)
     {
-      cursor_tree = tree_view->priv->tree;
-      cursor_node = cursor_tree->root;
       while (cursor_node && cursor_node->left != cursor_tree->nil)
        cursor_node = cursor_node->left;
     }
   else
     {
-      cursor_tree = tree_view->priv->tree;
-      cursor_node = cursor_tree->root;
       do
        {
          while (cursor_node && cursor_node->right != cursor_tree->nil)
@@ -9744,7 +9786,17 @@ gtk_tree_view_move_cursor_start_end (GtkTreeView *tree_view,
     }
 
   path = _gtk_tree_view_find_path (tree_view, cursor_tree, cursor_node);
-  gtk_tree_view_real_set_cursor (tree_view, path, TRUE, TRUE);
+
+  if (gtk_tree_path_compare (old_path, path))
+    {
+      gtk_tree_view_real_set_cursor (tree_view, path, TRUE, TRUE);
+    }
+  else
+    {
+      gtk_widget_error_bell (GTK_WIDGET (tree_view));
+    }
+
+  gtk_tree_path_free (old_path);
   gtk_tree_path_free (path);
 }
 
@@ -13888,28 +13940,36 @@ gtk_tree_view_search_key_press_event (GtkWidget *widget,
   /* select previous matching iter */
   if (event->keyval == GDK_Up || event->keyval == GDK_KP_Up)
     {
-      gtk_tree_view_search_move (widget, tree_view, TRUE);
+      if (!gtk_tree_view_search_move (widget, tree_view, TRUE))
+        gtk_widget_error_bell (widget);
+
       retval = TRUE;
     }
 
   if (((event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) == (GDK_CONTROL_MASK | GDK_SHIFT_MASK))
       && (event->keyval == GDK_g || event->keyval == GDK_G))
     {
-      gtk_tree_view_search_move (widget, tree_view, TRUE);
+      if (!gtk_tree_view_search_move (widget, tree_view, TRUE))
+        gtk_widget_error_bell (widget);
+
       retval = TRUE;
     }
 
   /* select next matching iter */
   if (event->keyval == GDK_Down || event->keyval == GDK_KP_Down)
     {
-      gtk_tree_view_search_move (widget, tree_view, FALSE);
+      if (!gtk_tree_view_search_move (widget, tree_view, FALSE))
+        gtk_widget_error_bell (widget);
+
       retval = TRUE;
     }
 
   if (((event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) == GDK_CONTROL_MASK)
       && (event->keyval == GDK_g || event->keyval == GDK_G))
     {
-      gtk_tree_view_search_move (widget, tree_view, FALSE);
+      if (!gtk_tree_view_search_move (widget, tree_view, FALSE))
+        gtk_widget_error_bell (widget);
+
       retval = TRUE;
     }
 
@@ -13927,7 +13987,10 @@ gtk_tree_view_search_key_press_event (GtkWidget *widget,
   return retval;
 }
 
-static void
+/*  this function returns FALSE if there is a search string but
+ *  nothing was found, and TRUE otherwise.
+ */
+static gboolean
 gtk_tree_view_search_move (GtkWidget   *window,
                           GtkTreeView *tree_view,
                           gboolean     up)
@@ -13942,15 +14005,17 @@ gtk_tree_view_search_move (GtkWidget   *window,
 
   text = gtk_entry_get_text (GTK_ENTRY (tree_view->priv->search_entry));
 
-  g_return_if_fail (text != NULL);
+  g_return_val_if_fail (text != NULL, FALSE);
+
+  len = strlen (text);
 
   if (up && tree_view->priv->selected_iter == 1)
-    return;
+    return strlen (text) < 1;
 
   len = strlen (text);
 
   if (len < 1)
-    return;
+    return TRUE;
 
   model = gtk_tree_view_get_model (tree_view);
   selection = gtk_tree_view_get_selection (tree_view);
@@ -13958,7 +14023,7 @@ gtk_tree_view_search_move (GtkWidget   *window,
   /* search */
   gtk_tree_selection_unselect_all (selection);
   if (!gtk_tree_model_get_iter_first (model, &iter))
-    return;
+    return TRUE;
 
   ret = gtk_tree_view_search_iter (model, selection, &iter, text,
                                   &count, up?((tree_view->priv->selected_iter) - 1):((tree_view->priv->selected_iter + 1)));
@@ -13967,6 +14032,7 @@ gtk_tree_view_search_move (GtkWidget   *window,
     {
       /* found */
       tree_view->priv->selected_iter += up?(-1):(1);
+      return TRUE;
     }
   else
     {
@@ -13976,6 +14042,7 @@ gtk_tree_view_search_move (GtkWidget   *window,
       gtk_tree_view_search_iter (model, selection,
                                 &iter, text,
                                 &count, tree_view->priv->selected_iter);
+      return FALSE;
     }
 }
 
index 392ebf51855fe88a949b780902f623d471aa9c4b..ae16cb8fd6829cd8ac8b4c141d5e83bec9ef36a0 100644 (file)
@@ -121,6 +121,7 @@ enum {
   CAN_ACTIVATE_ACCEL,
   GRAB_BROKEN,
   COMPOSITED_CHANGED,
+  KEYNAV_FAILED,
   LAST_SIGNAL
 };
 
@@ -204,6 +205,8 @@ static gboolean             gtk_widget_real_focus_out_event         (GtkWidget        *widget,
                                                                 GdkEventFocus    *event);
 static gboolean                gtk_widget_real_focus                   (GtkWidget        *widget,
                                                                 GtkDirectionType  direction);
+static gboolean                gtk_widget_real_keynav_failed           (GtkWidget        *widget,
+                                                                GtkDirectionType  direction);
 static PangoContext*   gtk_widget_peek_pango_context           (GtkWidget        *widget);
 static void            gtk_widget_update_pango_context         (GtkWidget        *widget);
 static void            gtk_widget_propagate_state              (GtkWidget        *widget,
@@ -807,6 +810,29 @@ gtk_widget_class_init (GtkWidgetClass *klass)
                  _gtk_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
 
+  /**
+   * GtkWidget::keynav-failed:
+   * @widget: the object which received the signal.
+   * @direction: the direction of movement
+   *
+   * See gtk_widget_keynav_failed() for details.
+   *
+   * Returns: %TRUE if stopping keyboard navigation is fine, %FALSE
+   *          if the emitting widget should try to handle the keyboard
+   *          navigation attempt in its parent container(s).
+   *
+   * Since: 2.12
+   **/
+  widget_signals[KEYNAV_FAILED] =
+    _gtk_binding_signal_new (I_("keynav-failed"),
+                             G_TYPE_FROM_CLASS (gobject_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_CALLBACK (gtk_widget_real_keynav_failed),
+                             _gtk_boolean_handled_accumulator, NULL,
+                             _gtk_marshal_BOOLEAN__ENUM,
+                             G_TYPE_BOOLEAN, 1,
+                             GTK_TYPE_DIRECTION_TYPE);
+
 /**
  * GtkWidget::delete-event:
  * @widget: the object which received the signal.
@@ -3642,7 +3668,7 @@ gtk_widget_real_mnemonic_activate (GtkWidget *widget,
     {
       g_warning ("widget `%s' isn't suitable for mnemonic activation",
                 G_OBJECT_TYPE_NAME (widget));
-      gdk_display_beep (gtk_widget_get_display (widget));
+      gtk_widget_error_bell (widget);
     }
   return TRUE;
 }
@@ -4326,6 +4352,35 @@ gtk_widget_real_focus (GtkWidget         *widget,
     return FALSE;
 }
 
+static gboolean
+gtk_widget_real_keynav_failed (GtkWidget        *widget,
+                               GtkDirectionType  direction)
+{
+  gboolean cursor_only;
+
+  switch (direction)
+    {
+    case GTK_DIR_TAB_FORWARD:
+    case GTK_DIR_TAB_BACKWARD:
+      return FALSE;
+
+    case GTK_DIR_UP:
+    case GTK_DIR_DOWN:
+    case GTK_DIR_LEFT:
+    case GTK_DIR_RIGHT:
+      g_object_get (gtk_widget_get_settings (widget),
+                    "gtk-keynav-cursor-only", &cursor_only,
+                    NULL);
+      if (cursor_only)
+        return FALSE;
+      break;
+    }
+
+  gtk_widget_error_bell (widget);
+
+  return TRUE;
+}
+
 /**
  * gtk_widget_is_focus:
  * @widget: a #GtkWidget
@@ -5899,6 +5954,92 @@ gtk_widget_child_focus (GtkWidget       *widget,
   return return_val;
 }
 
+/**
+ * gtk_widget_keynav_failed:
+ * @widget:    a #GtkWidget
+ * @direction: direction of focus movement
+ *
+ * This function should be called whenever keyboard navigation within
+ * a single widget hits a boundary. The function emits the
+ * "keynav-changed" signal on the widget and its return value should
+ * be interpreted in a way similar to the return value of
+ * gtk_widget_child_focus():
+ *
+ * When %TRUE is returned, stay in the widget, the failed keyboard
+ * navigation is Ok and/or there is nowhere we can/should move the
+ * focus to.
+ *
+ * When %FALSE is returned, the caller should continue with keyboard
+ * navigation outside the widget, e.g. by calling
+ * gtk_widget_child_focus() on the widget's toplevel.
+ *
+ * The default implementation for the "keynav-failed" signal is to
+ * return %TRUE for %GTK_DIR_TAB_FORWARD and
+ * %GTK_DIR_TAB_BACKWARD. For the other values of #GtkDirectionType,
+ * it looks at the "gtk-keynav-cursor-only" settings property and
+ * returns %FALSE if the setting is %TRUE. This way the entire GUI
+ * becomes cursor-navigatable on input devices such as mobile phones
+ * which only have cursor keys but no tab key.
+ *
+ * Whenever the default implementation returns %TRUE, it also calls
+ * gtk_widget_error_bell() to notify the user of the failed keyboard
+ * navigation.
+ *
+ * A use case for providing an own implementation of keynav-failed (by
+ * either connecting to it or by overriding it) would be a row of
+ * #GtkEntry widgets where the user should be able to navigate the
+ * entire row with the cursor keys, as e.g. known from GUIs that
+ * require entering license keys.
+ *
+ * Return value: %TRUE if stopping keyboard navigation is fine, %FALSE
+ *               if the emitting widget should try to handle the keyboard
+ *               navigation attempt in its parent container(s).
+ *
+ * Since: 2.12
+ **/
+gboolean
+gtk_widget_keynav_failed (GtkWidget        *widget,
+                          GtkDirectionType  direction)
+{
+  gboolean return_val;
+
+  g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
+
+  g_signal_emit (widget, widget_signals[KEYNAV_FAILED], 0,
+                direction, &return_val);
+
+  return return_val;
+}
+
+/**
+ * gtk_widget_error_bell:
+ * @widget: a #GtkWidget
+ *
+ * Notifies the user about an input-related error on this widget. If
+ * the gtk-error-bell settings property is %TRUE, it calls
+ * gdk_window_beep(), otherwise it does nothing.
+ *
+ * Note that the effect of gdk_window_beep() can be configured in many
+ * ways, depending on the windowing backend and the desktop environment
+ * or window manager that is used.
+ *
+ * Since: 2.12
+ **/
+void
+gtk_widget_error_bell (GtkWidget *widget)
+{
+  gboolean beep;
+
+  g_return_if_fail (GTK_IS_WIDGET (widget));
+
+  g_object_get (gtk_widget_get_settings (widget),
+                "gtk-error-bell", &beep,
+                NULL);
+
+  if (beep && widget->window)
+    gdk_window_beep (widget->window);
+}
+
 /**
  * gtk_widget_set_uposition:
  * @widget: a #GtkWidget
index 7cfc3df5cd37fc6028a63129443384dad18d5fa3..cb5841fbd3a97e2410568cbfc5918322ed639ecb 100644 (file)
@@ -408,7 +408,7 @@ struct _GtkWidgetClass
                                  GdkEventGrabBroken  *event);
 
   void         (* composited_changed) (GtkWidget *widget);
-       
+
   /* Padding for future expansion */
   void (*_gtk_reserved4) (void);
   void (*_gtk_reserved5) (void);
@@ -563,6 +563,9 @@ GdkWindow *gtk_widget_get_parent_window       (GtkWidget           *widget);
 
 gboolean   gtk_widget_child_focus         (GtkWidget           *widget,
                                            GtkDirectionType     direction);
+gboolean   gtk_widget_keynav_failed       (GtkWidget           *widget,
+                                           GtkDirectionType     direction);
+void       gtk_widget_error_bell          (GtkWidget           *widget);
 
 void       gtk_widget_set_size_request    (GtkWidget           *widget,
                                            gint                 width,